#ifndef BUFFERMANAGER_HPP
#define	BUFFERMANAGER_HPP

#include <cstdio>
#include <cstdlib>
#include <iostream>

using namespace std;

class bufferManager
{
	public:
		//Contructora de la cache que apunta a un disc.
		bufferManager(char* pPath);
		//Demana un punter a pàgina per poder consultar dades.
		bool query(int pRef, int pPag, char* pPunter);
		//Demana un punter a pàgina per poder consultar/modificar dades.
		bool update(int pRef, int pPag, char* pPunter);
		//Informa que ja no necessita la pàgina
		void unpin(int pRef, int pPag);
		//Demana informació del estat de la pàgina (bucket + pin + dirty).
		void stat(int pRef, int pPag);
		//Esto yo creo que sobra... el profe no se lo ha pensado bien.
		void pendent();

	private:
		struct buffer{
			char* data;
			int npag;
			int pin;
			bool dirty;
		};
		//Pool de 32 buffers.
		buffer _pool[32];
		//Punter a fitxer.
		FILE* _file;
		//Funció que busca el primer buffer lliure.
		int freeBuff();
		//Elimina el contingut d'un buffer.
		void removeBuff(int pBuf);
		//Exporta el contingut d'un buffer a un fitxer.
		void exportBuff(int pBuf);
		//Cerca una pàgina dins del pool. Si no la troba dóna "-1".
		int findBuff(int pPag);
};


//=============================================================================


bufferManager::bufferManager(char* pPath)
{
	//Obrim l'arxiu en mode modificació.
	_file = fopen(pPath, "rb+");
	for (int i = 0; i < 32; i++){
		_pool[i].data=new char[1024];
		_pool[i].pin=0;
		_pool[i].dirty=false;
		_pool[i].npag=-1;
	}
}


bool bufferManager::query(int pRef, int pPag, char* pPunter)
{
	int n = -1;
	n = findBuff(pPag);
	//Si la pagina és a memoria, apuntem a ella.
	if (n >= 0){
		pPunter = _pool[n].data;
		_pool[n].pin++;
		return true;
	//Si no és a memoria, busquem espai al pool per importar-la.
	}else{
		n = freeBuff();
		//Si hi ha espai, la posem
        if (n >= 0){
            //Apuntem al inici de la pàgina, al disc.
            fseek (_file, (1024 * pPag), SEEK_SET);
            //Llegim pàgina del disc i la coloquem.
            fread(_pool[n].data, 1, 1024, _file);
            pPunter = _pool[n].data;
            _pool[n].dirty = false;
            _pool[n].npag = pPag;
            //Pinejem la pàgina.
            _pool[n].pin++;
            return true;
        //Si no hi ha espai, error!
        }else{
            cout << "ERROR: Cache plena! (comanda "<< pRef << ")"<< endl;
            return false;
        }
	}
}


bool bufferManager::update(int pRef, int pPag, char* pPunter)
{
	int n = -1;
	char* pagina;
	//Si la pagina és al disc, apuntem a ella.
	if ((n = findBuff(pPag)) and (n >= 0)){
	    pagina = _pool[n].data;
	    return true;
	//En cas contrari, i si tenim espai al pool, la afegim.
	}else if((n = freeBuff()) and (n >= 0)){
		//Apuntem al inici de la pàgina, al disc.
		fseek (_file, (1024 * pPag), SEEK_SET);
		//Llegim pàgina del disc i la coloquem.
		fread(_pool[n].data, 1, 1024, _file);
		_pool[n].dirty = true;
		_pool[n].npag = pPag;
		//Pinejem la pàgina.
		_pool[n].pin++;
		return true;
	}
	return false;
}


void bufferManager::unpin(int pRef, int pPag)
{
	int n = findBuff(pPag);
	if(n >= 0) _pool[n].pin--;
}


void bufferManager::stat(int pRef, int pFrm)
{
  if (pFrm != 0) {
    cout << "[STAT frm " << pFrm << "] ";
    cout << "Pagina: " << _pool[pFrm].npag << " | ";
    cout << "PIN: " << _pool[pFrm].pin << " | ";
    cout << "dirty: " << _pool[pFrm].dirty << endl;
  } else {
    for (int i = 0; i < 32; i++){
	    cout << "[STAT frm " << i << "] ";
	    cout << "Pagina: " << _pool[i].npag << " | ";
	    cout << "PIN: " << _pool[i].pin << " | ";
	    cout << "dirty: " << _pool[i].dirty << endl;
    }
  }
}


int bufferManager::freeBuff()
{
	int n = -1;
	//Cerca el primer buffer buit o lliure.
	for(int i = 0; i<32 and n==-1; i++){
		if ( _pool[i].pin == 0 && _pool[i].dirty == false) n = i;
	}
	
	//Si totes estan utilitzades, busquem brutes
	if (n == -1) {
	  for(int i = 0; i<32 and n==-1; i++){
		  if ( _pool[i].pin == 0 && _pool[i].dirty == true){
		    removeBuff(i);
		    n = i;
		  }
	  }
	}
	
	return n;
}


void bufferManager::removeBuff(int pBuf)
{
  
	delete _pool[pBuf].data;
	_pool[pBuf].data = new char[1024];
	_pool[pBuf].npag = -1;
	_pool[pBuf].pin = 0;
	_pool[pBuf].dirty = false;
}


void bufferManager::exportBuff(int pBuf)
{
	fseek (_file, (1024 * _pool[pBuf].npag), SEEK_SET);
	fwrite(_pool[pBuf].data, 1024, 1, _file);
	_pool[pBuf].dirty = false;
}


int bufferManager::findBuff(int pPag)
{
	int n = -1;
	for(int i = 0; i<32 and n==-1; i++){
		if (_pool[i].npag == pPag) n = i;
	}
	return n;
}


#endif
